/*
 * =====================================================================================
 *
 *       Filename:  epcc.h
 *
 *    Description:  epcc.h
 *
 *         Author:  Younghyun Jo
 *   Organization:  Master Student
 *                  Computer Systems and Platforms Laboratory
 *                  Department of Computer Science and Engineering
 *                  Seoul National University
 *
 * =====================================================================================
 */

#include <stdint.h>
#include <cstdio>
#include <cstdlib>
#include <cmath>

#include <pthread.h>
#include <sys/time.h>
#include <tmc/cpus.h>
#include <tmc/task.h>

class ThreadParam {
  public:
    ThreadParam()
      : obj(NULL), id(0), cpu_id(0), numOperations(0) {}
    ~ThreadParam() {}

    void* obj; // own EPCC object
    size_t id; // own task number among launched tasks
    int cpu_id; // cpu affinity
    uint64_t numOperations; // # of operations for this task
};

template <class CStackClass> 
class EPCC {
  public:
    /* CStack, CCoarseGrainedLockStack, CLockFreeStack, CEliminationBackoffStack */
    //EPCC(const int numParticipants, const double pushRatio,
    //     const uint64_t numOperations, const long long delay)
    //  : _numParticipants(numParticipants), _pushRatio(pushRatio),
    //    _popRatio(1-pushRatio), _numOperations(numOperations), _delay(delay),
    //    _stack(new CStackClass())
    //{
    //  printPreamble();
    //}

    /* CCombTreeStack */
    EPCC(const int numParticipants, const double pushRatio,
         const uint64_t numOperations, const long long delay)
      : _numParticipants(numParticipants), _pushRatio(pushRatio),
        _popRatio(1-pushRatio), _numOperations(numOperations), _delay(delay),
        _stack(new CStackClass(numParticipants))
    {
      printPreamble();
    }

    /* CEliminationBackoffStack */
    /*
     * CEliminationBackoffStack needs more two parameters
     * capacity: the size of elimination array
     * timeout: the timeout long long value (nanosecond) in elimination array
     */
    EPCC(const int numParticipants, const double pushRatio,
         const uint64_t numOperations, const long long delay,
         const int capacity, const long long timeout)
      : _numParticipants(numParticipants), _pushRatio(pushRatio),
        _popRatio(1-pushRatio), _numOperations(numOperations), _delay(delay),
        _stack(new CStackClass(capacity, timeout))
    {
      /* CEliminationBackoffStack */
      printPreamble();
    }
    ~EPCC() { delete _stack; }

    CStackClass* GetCStack() { return _stack; }
    void measureCStackPerformance();

    const double _pushRatio; // push operation ratio
    const double _popRatio; // pop operation ratio
    const uint64_t _numOperations; // the number of operations for each thread
    const int _numParticipants; // the number of threads
    const long long _delay; // delay between operations(nanosecond)

  private:
    void printPreamble()
    {
      /* print basic information */
      printf(" Running CStackClass benchmark with %zuthreads\n", _numParticipants);
      printf("   PushRatio: %lf, PopRatio: %lf, NumOfOperations: %lu\n",
             _pushRatio, _popRatio, _numOperations);
    }

    CStackClass* const _stack; // the stack type for benchmark
};

//--------------------------------------------------------------------------------------
// The implementation of benchmark scenario
//

long long GetTimeStamp(void)
{
  static __thread struct timeval tv;
  gettimeofday(&tv, NULL);
  return 1000000LL * tv.tv_sec + tv.tv_usec;
}

int FindNextCpu(cpu_set_t* cpus, int& next_cpu)
{
  next_cpu++;
  while (next_cpu<TMC_CPUS_MAX_COUNT && !tmc_cpus_has_cpu(cpus, next_cpu)) {
    next_cpu++;
  }
  return next_cpu;
}

bool IsPushOper(double pushRatio)
{
  /* Determine whether Push or Pop */
  return ((double)rand()/(double)RAND_MAX) <= pushRatio;
}

#define MEASURE_OPS 0

template <class CStackClass> 
void* Benchmark(void* threadParam)
{
  ThreadParam* param = (ThreadParam*)threadParam;
  EPCC<CStackClass>* epcc = (EPCC<CStackClass>*)param->obj;
  CStackClass* const stack = epcc->GetCStack();
  const double pushRatio = epcc->_pushRatio;
  const long long delay = epcc->_delay;
  struct timespec sleep_time, rem;

  /* Make sure the affinity of this task */
  if (tmc_cpus_set_my_cpu(param->cpu_id) != 0) {
    perror("tmc_cpus_set_my_cpu(..) failed\n");
    exit(-1);
  }

  /* sleep_time: how many delays among operations */
  sleep_time.tv_sec = delay/100000000;
  sleep_time.tv_nsec = delay%100000000;
  srand((unsigned)time(NULL));

  for (uint64_t k=0; k<param->numOperations; k++) {
    bool Push = IsPushOper(pushRatio);

    if (Push) stack->Push(k, param->id); // Push and measure the latency
    else stack->Pop(param->id); // Pop and measure the latency

    nanosleep(&sleep_time, &rem); // operation interval
  }

  delete param;
  pthread_exit(NULL);
}

template <class CStackClass>
void EPCC<CStackClass>::measureCStackPerformance()
{
  long long time = 0;
  pthread_t threads[_numParticipants];
  /* Tilera specific CPU management data structure */
  cpu_set_t cpus;
  if (tmc_cpus_get_online_cpus(&cpus)) {
    perror("tmc_cpus_get_online_cpus failed\n");
    exit(1);
  }

  /* current task is just for handling other tasks */
  int next_cpu = -1;
  FindNextCpu(&cpus, next_cpu);

  /* Reserve one CPU for this EPCC task */
  if (tmc_cpus_set_my_cpu(next_cpu) != 0) {
    perror("tmc_cpus_set_my_cpu(..) failed\n");
    exit(1);
  }

  /* Spawn all participants */
  for (size_t i=1; i<_numParticipants+1; i++) {
    pthread_attr_t thread_attr;
    pthread_attr_init(&thread_attr);

    ThreadParam* param = new ThreadParam();
    param->obj = (void*)this;
    param->id = i-1;
    param->cpu_id = FindNextCpu(&cpus, next_cpu);
    param->numOperations = _numOperations;

    if (pthread_create(&threads[i], &thread_attr,
                       Benchmark<CStackClass>,
                       (void*)param)) {
      tmc_task_die("pthread_create(..) failed\n");
      exit(-1);
    }
  }

  /* Threads are running here */
  time = GetTimeStamp();
  for (size_t i=1; i<_numParticipants+1; i++) {
    if (pthread_join(threads[i], NULL) != 0) {
      tmc_task_die("pthread_join(..) failed\n");
      exit(-1);
    }
  }
  time = GetTimeStamp() - time;

  /* Display the results */
  printf("NumOfThreads: %d WorkingSet: %lu TotalOperations: %lu\n",
         _numParticipants, _numOperations, _numParticipants*_numOperations);
  printf("Total Elapsed Time : %lld (usec)\n", time);
}

